package crmdna.group;

import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.VoidWork;
import com.googlecode.objectify.Work;

import crmdna.client.Client;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.member.Member;
import crmdna.member.MemberCore.MemberProp;
import crmdna.sequence.Sequence;
import crmdna.sequence.Sequence.SequenceType;
import crmdna.user.User;
import crmdna.user.User.Action;
import crmdna.user.User.ResourceType;
import crmdna.user.UserCore.UserProp;

public class Group {
	public static class GroupProp {
		public long groupId;
		public String name;
		public String displayName;
	}

	public enum EmailType {
		REGISTRATION_CONFIRMATION, REGISTRATION_REMINDER
	}

	public static GroupProp create(String client, String displayName,
			String login) {

		Client.ensureValidClient(client);
		User.ensureAccess(client, login, ResourceType.GROUP, 0, Action.WRITE);

		String name = displayName.toLowerCase();

		List<Key<GroupEntity>> keys = ofy(client).load()
				.type(GroupEntity.class).filter("name", name).keys().list();

		if (keys.size() != 0)
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS,
					"There is already a group with name [" + displayName + "]");

		String key = getUniqueKey(client, name);
		long val = MemcacheServiceFactory.getMemcacheService().increment(key,
				1, (long) 0);

		if (val != 1)
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS,
					"There is already a group with name [" + displayName + "]");

		GroupEntity entity = new GroupEntity();
		entity.groupId = Sequence.getNext(client, SequenceType.GROUP);
		entity.name = name;
		entity.displayName = displayName;
		ofy(client).save().entity(entity).now();

		return entity.toProp();
	}

	private static String getUniqueKey(String namespace, String name) {
		return namespace + "_" + SequenceType.GROUP + "_" + name;
	}

	public static GroupEntity safeGet(String client, long groupId) {

		Client.ensureValidClient(client);

		GroupEntity entity = ofy(client).load().type(GroupEntity.class)
				.id(groupId).get();
		if (null == entity)
			throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST,
					"Group [" + groupId + "] does not exist");
		return entity;
	}

	public static GroupProp rename(String client, long groupId,
			String newDisplayName, String login) {
		Client.ensureValidClient(client);
		User.ensureAccess(client, login, ResourceType.GROUP, 0, Action.WRITE);

		GroupEntity groupEntity = safeGet(client, groupId);

		String newName = newDisplayName.toLowerCase();

		if (groupEntity.name.equals(newName)) {
			// ideally should be inside a transaction
			groupEntity.displayName = newDisplayName;
			ofy(client).save().entity(groupEntity).now();
			return groupEntity.toProp();
		}

		List<Key<GroupEntity>> keys = ofy(client).load()
				.type(GroupEntity.class).filter("name", newName).keys().list();
		if (keys.size() != 0)
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS,
					"There is already a center with name [" + newDisplayName
							+ "]");

		String key = getUniqueKey(client, newDisplayName);
		long val = MemcacheServiceFactory.getMemcacheService().increment(key,
				1, (long) 0);

		if (val != 1)
			throw new APIException(Status.ERROR_RESOURCE_ALREADY_EXISTS,
					"There is already a center with name [" + newDisplayName
							+ "]");

		// ideally should be inside a transaction
		groupEntity.name = newName;
		groupEntity.displayName = newDisplayName;
		ofy(client).save().entity(groupEntity).now();

		return groupEntity.toProp();
	}

	public static List<GroupProp> getAll(String client) {
		Client.ensureValidClient(client);

		List<GroupEntity> entities = ofy(client).load().type(GroupEntity.class)
				.order("name").list();

		List<GroupProp> props = new ArrayList<>();
		for (GroupEntity entity : entities)
			props.add(entity.toProp());

		return props;
	}

	public static Set<Long> getAllGroupIds(String client) {

		List<GroupProp> props = getAll(client);

		Set<Long> groupIds = new HashSet<>();
		for (GroupProp prop : props)
			groupIds.add(prop.groupId);

		return groupIds;
	}

	public static void ensureValidGroupIds(String client, List<Long> groupIds) {
		Set<Long> allGroupIds = Group.getAllGroupIds(client);
		for (int i = 0; i < groupIds.size(); i++) {
			long groupId = groupIds.get(i);
			if ((groupIds == null) || !allGroupIds.contains(groupId))
				throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST,
						"Error in record no [" + (i + 1) + "]: Group id ["
								+ groupIds.get(i) + "] does not exist");
		}
	}

	public static void delete(String client, long groupId, String login) {

		Client.ensureValidClient(client);
		User.ensureAccess(client, login, ResourceType.GROUP, 0, Action.WRITE);

		// there should not be any users for this center
		List<UserProp> userProps = User.getAllForGroup(client, groupId);
		if (userProps.size() != 0)
			throw new APIException(
					Status.ERROR_PRECONDITION_FAILED,
					"This center has ["
							+ userProps.size()
							+ "] users."
							+ "Please assign the users to a different center before deleting this center");

		// there should not be any members for this center
		List<MemberProp> memberProps = Member.getAllForGroup(client, groupId,
				login);
		if (memberProps.size() != 0)
			throw new APIException(
					Status.ERROR_PRECONDITION_FAILED,
					"This center has ["
							+ memberProps.size()
							+ "] members."
							+ "Please assign the members to a different center before deleting this center");

		throw new APIException(Status.ERROR_NOT_IMPLEMENTED,
				"This functionality is not implemented yet");

		// GroupEntity groupEntity = safeGet(client, groupId);

		// ofy(client).delete().entity(groupEntity).now();
	}

	public static PaypalApiCredentialsProp setPaypalApiCredentials(
			final String client, final long groupId, final String apiLogin,
			final String apiPwd, final String apiSecret, final Boolean disable,
			String login) {

		Client.ensureValidClient(client);

		User.ensureAccess(client, login, ResourceType.PAYPAL_CREDENTIALS, 0,
				Action.READWRITE);

		PaypalApiCredentialsProp prop = ofy(client).transact(
				new Work<PaypalApiCredentialsProp>() {

					@Override
					public PaypalApiCredentialsProp run() {
						GroupEntity groupEntity = Group
								.safeGet(client, groupId);

						boolean changed = false;

						if (apiLogin != null) {
							groupEntity.paypalApiLogin = apiLogin;
							changed = true;
						}

						if (apiPwd != null) {
							groupEntity.paypalApiPwd = apiPwd;
							changed = true;
						}

						if (apiSecret != null) {
							groupEntity.paypalApiSecret = apiSecret;
							changed = true;
						}

						if (disable != null) {
							groupEntity.paypalApiDisable = disable;
							changed = true;
						}

						Utils.ensureNotNullOrEmpty(groupEntity.paypalApiLogin,
								"Paypal login is null or empty");
						Utils.ensureNotNullOrEmpty(groupEntity.paypalApiPwd,
								"Paypal login is null or empty");
						Utils.ensureNotNullOrEmpty(groupEntity.paypalApiSecret,
								"Paypal secret is null or empty");

						if (changed)
							ofy(client).save().entity(groupEntity).now();

						PaypalApiCredentialsProp prop = new PaypalApiCredentialsProp();
						prop.login = groupEntity.paypalApiLogin;
						prop.pwd = groupEntity.paypalApiPwd;
						prop.secret = groupEntity.paypalApiSecret;
						prop.disable = groupEntity.paypalApiDisable;

						return prop;
					}
				});

		return prop;
	}

	public static MandrillConfigProp setMandrillConfig(final String client,
			final long groupId, final String apiKey, final String fromEmail,
			final String fromName, final Boolean disable, String login) {

		Client.ensureValidClient(client);

		User.ensureAccess(client, login, ResourceType.EMAIL_CONFIG, 0,
				Action.READWRITE);

		MandrillConfigProp mandrillConfigProp = ofy(client).transact(
				new Work<MandrillConfigProp>() {

					@Override
					public MandrillConfigProp run() {
						GroupEntity groupEntity = Group
								.safeGet(client, groupId);

						boolean changed = false;
						if (apiKey != null) {
							groupEntity.mandrillApiKey = apiKey;
							changed = true;
						}

						if (fromEmail != null) {
							Utils.ensureValidEmail(fromEmail.toLowerCase());
							groupEntity.mandrillFromEmail = fromEmail
									.toLowerCase();
							changed = true;
						}

						if (fromName != null) {
							groupEntity.mandrillFromName = fromName;
							changed = true;
						}

						if (disable != null) {
							groupEntity.mandrillDisableEmail = disable;
							changed = true;
						}

						if (changed)
							ofy(client).save().entity(groupEntity).now();

						MandrillConfigProp mandrillConfigProp = new MandrillConfigProp();
						mandrillConfigProp.apiKey = groupEntity.mandrillApiKey;
						mandrillConfigProp.fromEmail = groupEntity.mandrillFromEmail;
						mandrillConfigProp.fromName = groupEntity.mandrillFromName;

						return mandrillConfigProp;
					}
				});

		return mandrillConfigProp;
	}

	public static MandrillConfigProp getMandrillConfig(String client,
			long groupId, String login) {

		Client.ensureValidClient(client);

		GroupEntity groupEntity = Group.safeGet(client, groupId);

		User.ensureAccess(client, login, ResourceType.EMAIL_CONFIG, 0,
				Action.READWRITE);

		MandrillConfigProp mandrillConfigProp = new MandrillConfigProp();
		mandrillConfigProp.apiKey = groupEntity.mandrillApiKey;
		mandrillConfigProp.fromEmail = groupEntity.mandrillFromEmail;
		mandrillConfigProp.fromName = groupEntity.mandrillFromName;
		mandrillConfigProp.disableEmail = groupEntity.mandrillDisableEmail;

		return mandrillConfigProp;
	}

	static void setEmailHtmlTemplate(final String client, final long groupId,
			final EmailType emailType, final String emailTemplate, String login) {

		Client.ensureValidClient(client);

		User.ensureAccess(client, login, ResourceType.EMAIL_CONFIG, 0,
				Action.READWRITE);

		Utils.ensureNotNullOrEmpty(emailTemplate,
				"Email template cannot be null or empty");

		ofy(client).transact(new VoidWork() {

			@Override
			public void vrun() {
				GroupEntity groupEntity = Group.safeGet(client, groupId);

				if (emailType == EmailType.REGISTRATION_CONFIRMATION) {
					groupEntity.registrationConfirmationEmailTemplate = emailTemplate;
				} else if (emailType == EmailType.REGISTRATION_REMINDER) {
					groupEntity.registrationReminderEmailTemplate = emailTemplate;
				} else {
					throw new APIException(
							Status.ERROR_RESOURCE_INCORRECTLY_SPECIFIED,
							"Invalid emailType [" + emailType + "]");
				}

				ofy(client).save().entity(groupEntity).now();
			}
		});
	}

	static String getEmailTemplate(String client, long groupId,
			EmailType emailType) {

		Client.ensureValidClient(client);

		GroupEntity groupEntity = Group.safeGet(client, groupId);
		if (emailType == EmailType.REGISTRATION_CONFIRMATION) {
			return groupEntity.registrationConfirmationEmailTemplate;
		} else if (emailType == EmailType.REGISTRATION_REMINDER) {
			return groupEntity.registrationReminderEmailTemplate;
		} else {
			throw new APIException(Status.ERROR_RESOURCE_INCORRECTLY_SPECIFIED,
					"Invalid emailType [" + emailType + "]");
		}
	}

	public static PaypalApiCredentialsProp getPaypalApiCredentials(
			String client, long groupId, String login) {

		Client.ensureValidClient(client);

		GroupEntity groupEntity = Group.safeGet(client, groupId);

		User.ensureAccess(client, login, ResourceType.PAYPAL_CREDENTIALS, 0,
				Action.READWRITE);

		PaypalApiCredentialsProp prop = new PaypalApiCredentialsProp();
		prop.login = groupEntity.paypalApiLogin;
		prop.pwd = groupEntity.paypalApiPwd;
		prop.secret = groupEntity.paypalApiSecret;
		prop.disable = groupEntity.paypalApiDisable;

		return prop;
	}
}
